{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Time series example with advanced output writer usage"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial shows how specific results are extracted with the time series module in pandapower.\n",
    "\n",
    "In this example we define the output writer to log the following:\n",
    "* The maximum voltage of each medium voltage bus (vn_kv > 1.0 kV and vn_kv < 70.0) in an example grid\n",
    "* The sum of all p_kw values for every high voltage bus (vn_kv > 70.0 and vn_kv < 380.0)\n",
    "\n",
    "This time series calculation requires the minimum following inputs:\n",
    "* pandapower net\n",
    "* the time series (a Dataframe for example)\n",
    "* a pre defined output writer\n",
    "\n",
    "If you have just read the simple time_series jupyter notebook example. You can directly proceed to the section of create_output_writer()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we need some imports. Specific for this example are:\n",
    "* ConstControl -> \"constant\" controllers, which change the P and Q values of sgens and loads\n",
    "* DFData -> The Dataframe Datasource. This Dataframe holds the time series to be calculated\n",
    "* OutputWriter -> The output writer, which is required to write the outputs to the hard disk\n",
    "* run_timeseries -> the \"main\" time series function, which basically calls the controller functions (to update the P, Q of the ConstControllers) and runpp."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import tempfile\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "from pandapower.control import ConstControl\n",
    "from pandapower.timeseries import DFData, OutputWriter, run_timeseries\n",
    "from pandapower.run import set_user_pf_options\n",
    "from pandapower.create import (\n",
    "    create_empty_network,\n",
    "    create_bus,\n",
    "    create_ext_grid,\n",
    "    create_line,\n",
    "    create_load,\n",
    "    create_sgen,\n",
    "    create_transformer\n",
    ")\n",
    "rng = np.random.default_rng(10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "First we look at the time series example function. It follows these steps:\n",
    "1. create a simple test net\n",
    "2. create the datasource (which contains the time series P values)\n",
    "3. create the controllers to update the P values of the load and the sgen\n",
    "4. define the output writer and desired variables to be saved\n",
    "5. call the main time series function to calculate the desired results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def timeseries_example(output_dir):\n",
    "    # 1. create test net\n",
    "    net = simple_test_net()\n",
    "\n",
    "    # 2. create (random) data source\n",
    "    n_timesteps = 10\n",
    "    profiles, ds = create_data_source(n_timesteps)\n",
    "    # 3. create controllers (to control P values of the load and the sgen)\n",
    "    net = create_controllers(net, ds)\n",
    "\n",
    "    # time steps to be calculated. Could also be a list with non-consecutive time steps\n",
    "    time_steps = range(0, n_timesteps)\n",
    "\n",
    "    # 4. the output writer with the desired results to be stored to files.\n",
    "    ow = create_output_writer(net, time_steps, output_dir)\n",
    "\n",
    "    # 5. the main time series function\n",
    "    run_timeseries(net, time_steps)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We start by creating a simple example pandapower net consisting of five buses, a transformer, three lines, a load and a sgen. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def simple_test_net():\n",
    "    \"\"\"\n",
    "    simple net that looks like:\n",
    "\n",
    "    ext_grid b0---b1 trafo(110/20) b2----b3 load\n",
    "                                    |\n",
    "                                    |\n",
    "                                    b4 sgen\n",
    "    \"\"\"\n",
    "    net = create_empty_network()\n",
    "    set_user_pf_options(net, init_vm_pu = \"flat\", init_va_degree = \"dc\", calculate_voltage_angles=True)\n",
    "\n",
    "    b0 = create_bus(net, 110)\n",
    "    b1 = create_bus(net, 110)\n",
    "    b2 = create_bus(net, 20)\n",
    "    b3 = create_bus(net, 20)\n",
    "    b4 = create_bus(net, 20)\n",
    "\n",
    "    create_ext_grid(net, b0)\n",
    "    create_line(net, b0, b1, 10, \"149-AL1/24-ST1A 110.0\")\n",
    "    create_transformer(net, b1, b2, \"25 MVA 110/20 kV\", name='tr1')\n",
    "    create_line(net, b2, b3, 10, \"184-AL1/30-ST1A 20.0\")\n",
    "    create_line(net, b2, b4, 10, \"184-AL1/30-ST1A 20.0\")\n",
    "\n",
    "    create_load(net, b2, p_mw=15., q_mvar=10., name='load1')\n",
    "    create_sgen(net, b4, p_mw=20., q_mvar=0.15, name='sgen1')\n",
    "\n",
    "    return net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The data source is a simple pandas DataFrame. It contains random values for the load and the sgen P values (\"profiles\"). Of course your time series values should be loaded from a file later on.\n",
    "Note that the profiles are identified by their column name (\"load1_p\", \"sgen1_p\"). You can choose here whatever you prefer.\n",
    "The DFData(profiles) converts the Dataframe to the required format for the controllers. Note that the controller"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_data_source(n_timesteps=10):\n",
    "    profiles = pd.DataFrame()\n",
    "    profiles['load1_p'] = rng.random(n_timesteps) * 15.\n",
    "    profiles['sgen1_p'] = rng.random(n_timesteps) * 20.\n",
    "\n",
    "    ds = DFData(profiles)\n",
    "\n",
    "    return profiles, ds"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "create the controllers by telling the function which element_index belongs to which profile. In this case we map:\n",
    "* first load in dataframe (element_index=[0]) to the profile_name \"load1_p\"\n",
    "* first sgen in dataframe (element_index=[0]) to the profile_name \"sgen1_p\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_controllers(net, ds):\n",
    "    ConstControl(net, element='load', variable='p_mw', element_index=[0],\n",
    "                 data_source=ds, profile_name=[\"load1_p\"])\n",
    "    ConstControl(net, element='sgen', variable='p_mw', element_index=[0],\n",
    "                 data_source=ds, profile_name=[\"sgen1_p\"])\n",
    "    return net"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "create the output writer. Instead of saving the whole net (which takes a lot of time), we extract only pre defined outputs.\n",
    "In this case we:\n",
    "* save the results to the folder output_dir\n",
    "* write the results to \".xlsx\" Excel files. (Possible are: .json, .p, .csv - You should avoid Excel since it is slow)\n",
    "* log sum of real power (\"p_mw\") for each high voltage (hv) bus\n",
    "* log the maximum voltage magnitude (\"vm_pu\") of each medium voltage (mv) bus"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def create_output_writer(net, time_steps, output_dir):\n",
    "    ow = OutputWriter(net, time_steps, output_path=output_dir, output_file_type=\".xlsx\", log_variables=[])\n",
    "    \n",
    "    # create a mask to get the indices of all the hv buses in the grid \n",
    "    mask_hv_buses = (net.bus.vn_kv > 70.0) & (net.bus.vn_kv < 380.0)\n",
    "    hv_busses_index = net.bus.loc[mask_hv_buses].index\n",
    "    # create a mask to get the indices of all the mv buses in the grid\n",
    "    mask_mv_buses = (net.bus.vn_kv > 1.0) & (net.bus.vn_kv < 70.0)\n",
    "    mv_busses_index = net.bus.loc[mask_mv_buses].index\n",
    "    # now define the output writer, so that it gets the indices and specify the evaluation functions\n",
    "    # since we want the maximum voltage of all mv buses, we provide the indices of the mv buses and the maximum \n",
    "    # function np.max. The variable \"eval_name\" is free to chose and contains the name of the column in\n",
    "    # which the results are saved. \n",
    "    ow.log_variable('res_bus', 'p_mw', index=hv_busses_index, eval_function=np.sum, eval_name=\"hv_bus_sum_p\")\n",
    "    ow.log_variable('res_bus', 'vm_pu', index=mv_busses_index, eval_function=np.max, eval_name=\"mv_bus_max\")\n",
    "    return ow"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now lets execute the code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "output_dir = os.path.join(tempfile.gettempdir(), \"time_series_example\")\n",
    "print(\"Results can be found in your local temp folder: {}\".format(output_dir))\n",
    "if not os.path.exists(output_dir):\n",
    "    os.mkdir(output_dir)\n",
    "timeseries_example(output_dir)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If everything works you should have the desired results the temporary folder of your os (see print statement above).\n",
    "In this folder two excel files should have appeared containing the desired output for each of the ten time steps"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot results\n",
    "Now let us plot the results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline  \n",
    "\n",
    "# voltage results\n",
    "vm_pu_file = os.path.join(output_dir, \"res_bus\", \"vm_pu.xlsx\")\n",
    "vm_pu = pd.read_excel(vm_pu_file, index_col=0)\n",
    "vm_pu.plot(label=\"vm_pu\")\n",
    "plt.xlabel(\"time step\")\n",
    "plt.ylabel(\"voltage mag. [p.u.]\")\n",
    "plt.title(\"Voltage Magnitude\")\n",
    "plt.grid()\n",
    "plt.show()\n",
    "\n",
    "\n",
    "# p_mw results\n",
    "p_mw_file = os.path.join(output_dir, \"res_bus\", \"p_mw.xlsx\")\n",
    "p_mw = pd.read_excel(p_mw_file, index_col=0)\n",
    "p_mw.plot(label=\"p_mw\")\n",
    "plt.xlabel(\"time step\")\n",
    "plt.ylabel(\"P [MW]\")\n",
    "plt.title(\"Real Power at Buses\")\n",
    "plt.grid()\n",
    "plt.show()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
